In diesem Abschnitt erhalten wir einen ersten Überblick über die Grundlagen von SQL und erfahren, warum diese Sprache so wichtig für die Arbeit mit Datenbanken ist. SQL ist eine Abkürzung für Structured Query Language. SQL ist eine Datenbanksprache, mit der man Datenstrukturen in sogenannten relationalen Datenbanken definieren kann. Das bedeutet, mit SQL legt man zum Beispiel fest, wie Tabellen aufgebaut sind und wie die Daten darin organisiert werden. Mit SQL kann man Datenstrukturen definieren, also zum Beispiel festlegen, welche Tabellen und Felder es in einer Datenbank gibt. Außerdem kann man mit SQL Daten abfragen, einfügen, ändern oder löschen – also den gesamten Datenbestand bearbeiten. SQL basiert auf der sogenannten relationalen Algebra. Das ist ein mathematisches Konzept, das beschreibt, wie Daten in Tabellenform miteinander verknüpft werden können. Die Syntax von SQL ist vergleichsweise einfach gehalten und orientiert sich stark an der englischen Alltagssprache. Das macht es auch für Einsteigerinnen und Einsteiger leichter, die Sprache zu erlernen und anzuwenden. Ein internationales Gremium, an dem auch nationale Normungsorganisationen beteiligt sind, arbeitet ständig daran, SQL weiterzuentwickeln und zu standardisieren. Fast alle bekannten Datenbanksysteme unterstützen SQL – allerdings gibt es dabei oft kleine Unterschiede, weil viele Hersteller eigene Dialekte oder Erweiterungen verwenden. Trotzdem ist SQL die gemeinsame Basis für die Arbeit mit relationalen Datenbanken. In MariaDB gibt es verschiedene Datentypen, um Zahlen in einer Datenbank zu speichern – einer der wichtigsten Bereiche sind dabei die Ganzzahlen. In MariaDB existieren verschiedene Datentypen, um Ganzzahlen zu speichern. Der kleinste Ganzzahltyp heißt tiny-int und benötigt ein Byte Speicherplatz. Mit tiny-int kann man entweder Werte von null bis zweihundert-fünfundfünfzig oder von minus einhundert-achtundzwanzig bis einhundertsieben-undzwanzig speichern. Der nächste Typ ist small-int, der zwei Bytes belegt. Mit small-int lassen sich Ganzzahlen von null bis fünfundsechzig-tausend-fünfhundert-fünfunddreißig oder von minus zweiunddreißig-tausend-siebenhundert-achtundsechzig bis zweiunddreißig-tausend-siebenhundert-siebenundsechzig speichern. Medium-int ist der nächste Typ und verwendet drei Bytes. Hier liegt der Wertebereich bei null bis sechzehn-millionen-siebenhundert-siebenundsiebzig-tausend-zweihundert-fünfzehn oder bei minus acht-millionen-dreihundert-achtundachtzig-tausend-sechshundert-acht bis acht-millionen-dreihundert-achtundachtzig-tausend-sieben. Der am häufigsten verwendete Typ ist int, der vier Bytes groß ist. Mit int kann man Zahlen von null bis ungefähr vier-millionen-dreihundert-tausend oder von ungefähr minus zwei-millionen-einhundert-tausend bis ungefähr zwei-millionen-einhundert-tausend speichern. Für besonders große Zahlen gibt es big-int, der acht Bytes belegt. Big-int ermöglicht Werte von null bis zwei hoch vierundsechzig minus eins oder von minus zwei hoch dreiundsechzig bis zwei hoch dreiundsechzig minus eins. Standardmäßig sind diese Ganzzahltypen als signed, also mit Vorzeichen, definiert. Wenn man den SQL-Befehl unsigned verwendet, werden nur positive Werte gespeichert und das Vorzeichenbit steht zusätzlich für Zahlen zur Verfügung, wodurch sich der Wertebereich nach oben verschiebt. Neben Ganzzahlen gibt es in MariaDB auch spezielle Datentypen, um Kommazahlen, also Zahlen mit Nachkommastellen, zu speichern. Der erste wichtige Typ ist float. Float belegt vier Bytes Speicherplatz und steht für eine Fließkommazahl mit Vorzeichen. Das bedeutet, dass sowohl positive als auch negative Werte mit Nachkommastellen gespeichert werden können. Für noch mehr Genauigkeit gibt es den Typ double. Double verwendet acht Bytes und ermöglicht Fließkommazahlen mit Vorzeichen und doppelter Genauigkeit. Das ist besonders nützlich, wenn sehr präzise Werte benötigt werden, zum Beispiel bei wissenschaftlichen Berechnungen. Ein weiterer wichtiger Datentyp ist decimal. Decimal steht für Festkommazahlen mit Vorzeichen. Das Besondere an decimal ist, dass dieser Typ exakte numerische Werte speichert, ohne die typischen Rundungsfehler von Fließkommazahlen. Deshalb wird decimal häufig für Geldbeträge oder andere Werte verwendet, bei denen Genauigkeit besonders wichtig ist. Für die Speicherung von Datums- und Zeitwerten bietet MariaDB verschiedene spezielle Datentypen an. Der Datentyp date benötigt drei Bytes Speicherplatz und speichert ein Datum im Format Jahr-Monat-Tag, also zum Beispiel zweitausend-fünfundzwanzig Bindestrich null-fünf Bindestrich neunzehn. Der Wertebereich reicht dabei vom ersten Januar des Jahres eintausend bis zum einunddreißigsten Dezember des Jahres neuntausend-neunhundert-neunundneunzig. Der Typ datetime verwendet acht Bytes und speichert zusätzlich zur Datumsangabe auch die genaue Uhrzeit, und zwar im Format Jahr-Monat-Tag Stunde-Doppelpunkt-Minute-Doppelpunkt-Sekunde. Der Wertebereich für datetime entspricht dem von date. Ein weiterer Typ ist timestamp, der vier Bytes belegt und einen Zeitstempel speichert. Der Wertebereich von timestamp reicht vom ersten Januar neunzehnhundert-siebzig bis zum neunzehnten Januar zweitausend-achtunddreißig. Für die reine Speicherung von Zeitangaben gibt es den Typ time, der drei Bytes benötigt. Mit time kann man Zeitspannen zwischen minus achthundert-achtunddreißig Stunden neunundfünfzig Minuten neunundfünfzig Sekunden und achthundert-neununddreißig Stunden neunundfünfzig Minuten neunundfünfzig Sekunden speichern. Die Ausgabe erfolgt dabei immer im Format Stunde-Doppelpunkt-Minute-Doppelpunkt-Sekunde. Schließlich gibt es noch den Typ year, der nur ein Byte belegt und ein Jahr zwischen neunzehnhundert-eins und zweitausend-einhundert-fünfundfünfzig speichern kann. Mit diesen Datentypen lassen sich alle wichtigen Informationen zu Datum und Uhrzeit in einer MariaDB-Datenbank abbilden. In MariaDB gibt es neben Zahlenwerten auch spezielle Datentypen für Zeichen, Zeichenketten und Binärdaten. Der Datentyp char steht für eine Zeichenkette mit fester Länge. Das bedeutet, die Länge des gespeicherten Textes ist immer gleich, egal wie viele Zeichen tatsächlich verwendet werden. Varchar hingegen steht für eine Zeichenkette variabler Länge. Hier wird nur so viel Speicherplatz genutzt, wie tatsächlich für den Text benötigt wird, bis zu einer festgelegten Maximalgröße. Für die Speicherung von Binärdaten, wie zum Beispiel Bildern oder Dateien, gibt es den Typ blob. Blob steht für Binary Large Object und kann bis zu fünfundsechzigtausendfünfhundertfünfunddreißig Bytes speichern. Weitere Varianten sind tiny-blob mit bis zu zweihundertfünfundfünfzig Bytes, medium-blob mit bis zu sechzehnmillionensiebenhundertsiebenundsiebzigtausendzweihundertfünfzehn Bytes und long-blob mit bis zu viermilliardenzweihundertvierundneunzigmillionensechshundertdreiundneunzigtausendzweihundertfünfundneunzig Bytes. Diese Datentypen ermöglichen es, sowohl kurze Texte als auch sehr große Datenmengen effizient in einer Datenbank zu speichern. SQL-Befehle lassen sich in vier Kategorien einteilen, je nachdem, wofür sie verwendet werden. Die Data Manipulation Language, abgekürzt DML, umfasst alle Befehle, mit denen man Daten in einer Datenbank einfügen, ändern oder löschen kann. Weiterhin gibt es die Data Definition Language, kurz DDL, die verwendet wird, um das Schema einer Datenbank zu definieren, also zum Beispiel Tabellen oder Felder anzulegen oder zu verändern. Mit der Data Control Language, kurz DCL, werden Rechte vergeben und die Transaktionskontrolle gesteuert. Die Data Retrieval Language, abgekürzt DRL, ist für das Lesen von Daten zuständig. Sie wird manchmal auch als Data Query Language, beziehungsweise DQL, bezeichnet. In diesem Abschnitt erhalten wir einen Überblick über DRL und DQL und wie wir einfache Abfragen mit dem SELECT-Befehl formulieren. Dargestellt ist ein beispielhafter Datenbestand in einem ER-Diagramm. Entitäten sind hier Student, Vorlesung sowie Professor. Den Entitäten sind Attribute zugeordnet, hier beispielsweise das Attribut Matrikelnummer der Entität Student. Wir erkennen an dem Unterstrich, dass es sich hierbei um einen Primärschlüssel handelt. Auch die beiden weiteren Entitäten verfügen jeweils über einen Primärschlüssel. Die Entitäten stehen in Beziehungen zueinander: Studenten hören Vorlesungen und in Vorlesungen sitzen viele Studenten. Eine Vorlesung wird von einem Professor gehalten und ein Professor kann mehrere Vorlesungen halten. Ein besonders wichtiger Befehl in SQL ist SELECT. Mit SELECT kann man Daten aus einer oder mehreren Tabellen abfragen und anzeigen lassen. Die einfachste Form ist SELECT Sternchen FROM Tabellenname. Das Sternchen steht dabei als Platzhalter für alle Spalten der Tabelle. Hier im Beispiel werden durch die Abfrage alle Spalten und alle Zeilen aus der Tabelle Student angezeigt. Das Ergebnis ist eine vollständige Liste aller gespeicherten Datensätze dieser Tabelle. Man kann bei einer SELECT-Abfrage auch gezielt nur bestimmte Spalten auswählen, die angezeigt werden sollen. Dazu gibt man hinter SELECT die gewünschten Spaltennamen an, getrennt durch Kommata, und schreibt dahinter FROM und den Tabellennamen. Unser Beispiel hier gibt uns somit alle Vorlesungsnummern und Titel aus der Tabelle Vorlesung aus. So kann man sich gezielt nur die Informationen anzeigen lassen, die man gerade braucht, anstatt sich immer alle Daten anzusehen. Mit dem Schlüsselwort DISTINCT kann man bei einer SELECT-Abfrage dafür sorgen, dass doppelte Werte in der Ergebnisliste entfernt werden. Das ist zum Beispiel nützlich, wenn man wie in diesem Beispiel wissen möchte, welche Matrikelnummern in einer Tabelle vorkommen, ohne dass jede Nummer mehrfach angezeigt wird. Man schreibt dann SELECT DISTINCT Spaltenname FROM Tabelle. Das Ergebnis enthält dann wie hier jede Matrikelnummer nur ein einziges Mal, auch wenn sie mehrfach in der Tabelle vorkommt. In SQL kann man mit dem Schlüsselwort "as" den Namen einer Spalte in der Ergebnisliste ändern, um die Anzeige zu vereinfachen oder zu verbessern. Das macht man zum Beispiel so: SELECT Matr-Nummer "as" Matrikelnummer, Name FROM Student. Dadurch wird die Spalte Matr-Nummer in der Ausgabe als Matrikelnummer angezeigt. Dies ist besonders hilfreich, wenn man die Ergebnisse für andere Personen verständlicher gestalten möchte oder wenn die Originalnamen der Spalten nicht selbsterklärend sind. Mit dem Schlüsselwort WHERE kann man eine SELECT-Abfrage um einen Filter ergänzen, sodass nur bestimmte Zeilen angezeigt werden. Hier im Beispiel wollen wir uns alle Vorlesungsnummern anzeigen lassen, die genau mit dem Titel ET gekennzeichnet sind. Alle weiteren Zeilen werden nicht angezeigt. WHERE ist ein zentrales Werkzeug, um sich gezielt bestimmte Daten anzeigen zu lassen und große Tabellen zu durchsuchen. Das Schlüsselwort LIKE ermöglicht es, in einer WHERE-Bedingung nach bestimmten Mustern in Texten zu suchen. Man kann damit zum Beispiel alle Namen finden, die mit einem bestimmten Buchstaben beginnen. In unserem Beispiel wollen wir uns alle Namen ausgeben lassen, die mit dem Buchstaben F beginnen. Das Prozentzeichen steht dabei für eine beliebige Zeichenfolge und der Unterstrich für ein fehlendes Zeichen. So kann man flexibel die Felder nach ihrem Inhalt durchsuchen. Mit ORDER BY kann man die Ergebnisse einer SELECT-Abfrage sortieren. Standardmäßig wird nach der angegebenen Spalte aufsteigend sortiert, das heißt von A bis Z oder von klein nach groß. Man kann auch explizit asc für aufsteigend oder desc für absteigend angeben. In unserem Beispiel werden alle Studierenden alphabetisch nach ihrem Namen sortiert. Dies ist besonders nützlich, wenn man große Datenmengen übersichtlich darstellen möchte. In diesem Abschnitt erhalten wir einen Überblick darüber, wie man mit der Data Retrieval Language komplexere Abfragen über mehrere Tabellen hinweg durchführt, indem man verschachtelte SELECT-Abfragen einsetzt. SQL ermöglicht auch komplexere Abfragen, bei denen Daten aus mehreren Tabellen kombiniert werden. Dazu werden nach dem FROM den Quell-Tabellen Kürzel zugewiesen, welche zur Differenzierung der Tabellen und ihrer Attribute genutzt werden. Nach dem SELECT können anhand der Kürzel und der Attributnamen die gewünschten Spalten definiert werden. Hinter dem WHERE zeigt sich auch, warum die Vergabe der Kürzel sinnvoll ist. Beide zu verknüpfenden Tabellen besitzen die Spalte Pers Nummer. Nur durch die zugewiesenen Kürzel lassen sich diese hier differenzieren. Angezeigt werden am Ende nur die Datensätze, bei denen der Wert für Pers Nummer in beiden Tabellen übereinstimmt. Das eben erklärte Verfahren nennt sich "Join". Bei einem "Join" sollen Daten zusammengeführt und angezeigt werden. Dies wird durch eine Verknüpfung von Schlüsselfeldern über mehrere Tabellen hinweg realisiert. Man spricht hier auch von einem "Natural-Join", also einem inneren natürlichen Verbund, welcher ein Spezialfall des "Inner-Join" ist. Eine detaillierte Betrachtung von "Joins" wird im nächsten Kapitel durchgeführt. Beginnen wir ein neues Beispiel. Es gibt das Studierenden-Verwaltungs-System, kurz S-V-S. In seiner ersten Version besteht S-V-S aus Veranstaltungen, Teams und Studierenden. Für diese gilt folgendes. In einer Veranstaltung können beliebig viele Studierende und Teams sein. Ein Team wiederum besteht aus beliebig vielen Studierenden. Studierende können jedoch auch Teil einer Veranstaltung sein, ohne Teil eines Teams sein zu müssen. Abgebildet wird das S-V-S in der Datenbank durch fünf Tabellen. Die erste Tabelle heißt Benutzer und führt als Primärschlüssel das Attribut id benutzer. Die Spalte Nall zeigt, dass dieses Attribut nicht leer sein darf. Die meisten übrigen Attribute, mit denen Informationen wie zum Beispiel der Vorname eines Studierenden gespeichert werden, sowie auch der Primärschlüssel selbst sind vom Datentyp varchar und sind zwischen eins und einhundert Zeichen lang. Das Attribut aktiv weicht davon ab und wird durch einen tinyint der Länge eins dargestellt, welcher binär den Status eines Benutzers abbildet. Dies entspricht zum Beispiel einem true oder false. Die Tabelle Veranstaltung ist ähnlich aufgebaut, verfügt jedoch über Attribute, welche die Veranstaltung definieren. Dazu zählen beispielsweise die maximalen Punkte oder die Anzahl an Tests. Auch über einen Primärschlüssel verfügt diese Tabelle, die ID der Veranstaltung. Diese wird beim Anlegen eines neuen Datensatzes automatisch hochgezählt, da in der Spalte Extra Auto-Inkrement definiert ist. Das Attribut name stellt hier einen Sekundärschlüssel der Tabelle dar. Verknüpft werden die Tabelle Veranstaltung und die Tabelle Benutzer durch eine weitere Tabelle Student In Veranstaltung. In dieser weiteren Tabelle werden die IDs der Benutzer den IDs der Veranstaltungen zugeordnet. Es handelt sich hierbei um Fremdschlüssel. Darüber hinaus existiert noch eine Tabelle Team, welche ebenfalls eine ID als Primärschlüssel sowie die Attribute eines Teams führt. Eines dieser Attribute ist der Fremdschlüssel ID Veranstaltung, der ein Team einer Veranstaltung zuordnet. Zuletzt existiert eine Tabelle Student in Team, in welcher anhand der Fremdschlüssel ID Benutzer und ID Team eine Zuordnung von Studenten zu Teams erfolgt. Durch die Aufteilung von verschachtelten SQL-Statements in der Anwendungslogik kann jedes SQL-Statement einzeln getestet werden. Der SQL-Code bleibt übersichtlicher und besser wartbar. Andererseits erhält man vielleicht nicht das effizienteste Ergebnis aus Sicht der Performance und der Server-Last. Die Wartbarkeit verringert aber die Anzahl der notwendigen Personal-Stunden. In der Regel sind Personal-Stunden heutzutage teurer als Hardware-Kapazität. Erst bei hoch skalierten Systemen kann dies anders herum sein. Eine Implementierung der SELECT-Abfragen in Java könnte zum Beispiel wie folgt aussehen. Hier werden in die SQL-Befehle Variablen eingesetzt, sodass die Abfragen dynamisch integriert sind. Die Methode get Team Des Studierenden selektiert zunächst anhand der Benutzer ID die Team ID über die Tabelle Student-in-Team. Daraufhin werden mithilfe der ermittelten Team-ID alle Attribute des Teams selektiert, wobei ebenfalls die Veranstaltungs ID abgeglichen wird. Das Schlüsselwort DISTINCT sorgt dafür, dass keine Duplikate ausgegeben werden, sondern nur einzigartige Datensätze. Umgekehrt kann die Methode get Studierende des Teams die Studierenden ermitteln, welche einem Team einer bestimmten Veranstaltung zugeordnet sind. Anhand der mit get ID ermittelten Team-ID werden zunächst alle Benutzer ausgegeben, welche in der Tabelle Student in Team der Team-ID zugewiesen sind. Hierbei wird ebenfalls wieder das DISTINCT-Schlüsselwort verwendet. Daraufhin werden die Benutzerdaten aller zuvor ermittelten Benutzer IDs aus der Tabelle Benutzer abgerufen, welche dabei zuerst nach Nachnamen und dann nach Vornamen sortiert wird. Die Methode get Veranstaltungen des Studierenden ermittelt alle Veranstaltungen, für die eine bestimmte Benutzer ID eingetragen ist. Mithilfe der Benutzer ID wird in der Tabelle Student in Veranstaltung die ID der Veranstaltungen ermittelt. Diese wird dann benutzt, um in der Tabelle Veranstaltung die Veranstaltungen auszuwählen, welche den zuvor ermittelten IDs entsprechen. Danach erfolgt eine Sortierung nach Veranstaltungsnamen. Diese Methode soll Veranstaltungen ermitteln, welche nicht von einer bestimmten Benutzer-ID besucht werden. Dazu werden zunächst die Veranstaltungen ermittelt, welche der Studierende besucht. Anhand dieser Zusammenstellung können dann die Daten der Veranstaltungen abgerufen werden, welche nicht in dieser Teilmenge liegen. Hierfür wird das Schlüsselwort "not-in" verwendet. Ebenfalls wird hier nach dem Namen der Veranstaltung sortiert. In dieser Methode sollen alle Studentendaten von Studierenden abgerufen werden, welche nicht an der übergebenen Veranstaltung teilnehmen. Zuerst wird dafür die Gesamtmenge aller Studierenden-IDs ermittelt. Danach werden die Studierenden ermittelt, welche an der übergebenen Veranstaltung teilnehmen. Zuletzt werden genau die Studierendendatensätze selektiert, welche zwar in der Gesamtmenge der Studierenden, aber nicht in der Teilmenge der Veranstaltung vorhanden sind. Diese Selektion wird auch nach Nachnamen und Vornamen sortiert. Dieses Beispiel zeigt, wie Studierende selektiert werden können, die in einer Veranstaltung keinem Team zugeordnet sind. Die erste SQL-Abfrage bestimmt alle Teams einer bestimmten Veranstaltung. Darauf werden alle Studierenden ermittelt, welche Teil eines dieser Teams sind. Studierende, die nicht in dieser Teilmenge sind, stellen daher diejenigen dar, die wir suchen. Diese werden nun selektiert und zuletzt von diesen alle Daten sortiert abgerufen. Das letzte Beispiel stellt dar, wie die Daten von Studierenden ermittelt werden können, welche in derselben Veranstaltung, aber nicht im bestimmten Team sind. Zunächst werden alle Teams der Veranstaltung ermittelt, die nicht dem übergebenen Team entsprechen. Umgesetzt wird dies durch den Ungleich-Operator kleiner als größer als. Diese Teams werden dann verwendet, um alle Studierenden zu ermitteln, die diesen Teams zugeordnet sind. Zuletzt werden aus der Benutzer-Tabelle